package mental_connect.backend.backend_ym.config;

import lombok.extern.slf4j.Slf4j;
import mental_connect.backend.backend_ym.entity.AccountInfo;
import mental_connect.backend.backend_ym.filter.JwtTokenFilter;
import mental_connect.backend.backend_ym.service.AccountInfoDetailService;
import mental_connect.backend.backend_ym.service.JwtTokenService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.HttpStatus;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.InsufficientAuthenticationException;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.Authentication;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Optional;
import java.util.Set;

@Slf4j
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled = true)
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    private JwtTokenFilter jwtTokenFilter;
    @Autowired
    private AccountInfoDetailService accountInfoDetailService;
    @Autowired
    @Qualifier("accountInfo")
    private RedisTemplate<String, AccountInfo> redisTemplate;

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(accountInfoDetailService).passwordEncoder(passwordEncoder());
    }

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .csrf().disable()
                .sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .authorizeRequests()
                .antMatchers("/login").permitAll()
                .antMatchers("/register").permitAll()
                .anyRequest().authenticated()
                .and()
                .exceptionHandling()
                .accessDeniedHandler((httpServletRequest, httpServletResponse, e) -> {
                    httpServletResponse.setStatus(HttpStatus.UNAUTHORIZED.value());
                    httpServletResponse.setContentType("application/json");
                    httpServletResponse.setCharacterEncoding("UTF8");
                    httpServletResponse.getWriter().write("{\"code\":"+HttpStatus.FORBIDDEN.value()+",\"message\":\"没有权限\"}");
                })
                .authenticationEntryPoint((httpServletRequest, httpServletResponse, e) -> {
                    if(e instanceof BadCredentialsException){
                        httpServletResponse.setStatus(HttpStatus.UNAUTHORIZED.value());
                        httpServletResponse.setContentType("application/json");
                        httpServletResponse.setCharacterEncoding("UTF8");
                        httpServletResponse.getWriter().write("{\"code\":"+HttpStatus.UNAUTHORIZED.value()+",\"message\":\"用户不存在或密码错误\"}");
                    }else if(e instanceof InsufficientAuthenticationException){
                        httpServletResponse.setStatus(HttpStatus.UNAUTHORIZED.value());
                        httpServletResponse.setContentType("application/json");
                        httpServletResponse.setCharacterEncoding("UTF8");
                        httpServletResponse.getWriter().write("{\"code\":"+HttpStatus.UNAUTHORIZED.value()+",\"message\":\"无效token\"}");
                    }else{
                        httpServletResponse.setStatus(HttpStatus.UNAUTHORIZED.value());
                        httpServletResponse.setContentType("application/json");
                        httpServletResponse.setCharacterEncoding("UTF8");
                        httpServletResponse.getWriter().write("{\"code\":"+HttpStatus.FORBIDDEN.value()+",\"message\":\"未知异常\"}");
                    }
                })
                .and()
                .formLogin().disable()
                .addFilterBefore(jwtTokenFilter,UsernamePasswordAuthenticationFilter.class)
                .logout()
                .logoutUrl("/logout") // 登出接口
                .logoutSuccessHandler(new LogoutSuccessHandler() {
                    private boolean isLoggedOut = false;
                    @Override
                    public void onLogoutSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException {
                        if (!isLoggedOut) {
                            isLoggedOut = true;
                            String token = httpServletRequest.getHeader("token");
                            String redisKey = JwtTokenService.getRedisKey(token);
                            String[] split = redisKey.split("_");
                            Set<String> keys = redisTemplate.keys("*" + split[1]);
                            Optional.ofNullable(keys)
                                    .ifPresent(i -> i.forEach(k -> redisTemplate.delete(k)));
                            httpServletResponse.setContentType("application/json");
                            httpServletResponse.setCharacterEncoding("UTF8");
                            httpServletResponse.getWriter().write("{\"code\":"+ HttpStatus.OK.value()+",\"message\":\"登出成功\"}");
                        } else {
                            httpServletResponse.setContentType("application/json");
                            httpServletResponse.setCharacterEncoding("UTF8");
                            httpServletResponse.getWriter().write("{\"code\":"+ HttpStatus.FORBIDDEN.value()+",\"message\":\"请勿重复登出\"}");
                        }
                    }
                })
        ;
    }

    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }
}
